﻿using System;
using System.Collections.Generic;
using Microsoft.Practices.Unity;

namespace SmartFront.Toolkit.Core
{
    /// <summary>
    /// Base class used for all dependency injection based factories.
    /// </summary>
    /// <typeparam name="TFactory">The type of the factory.</typeparam>
    /// <typeparam name="T">The type the factory is being used to create.</typeparam>
    /// <remarks>
    /// DependencyInjectionFactories are created as singletons per thread.
    /// </remarks>
    public abstract class DependencyInjectionFactory<TFactory, T> : IDependencyInjectionFactory<T>
    {
        #region Static Implementation

        /// <summary>
        /// Private field for the singleton instance.
        /// </summary>
        /// <remarks>
        /// Is thread static, because we don't want factories shared between threads.  Allows
        /// us to avoid locking for the <see cref="Dependencies"/> property.
        /// </remarks>
        //[ThreadStatic]
        private static IDependencyInjectionFactory<T> factorySingleton;

        /// <summary>
        /// Gets the singleton instance.
        /// </summary>
        /// <value>The instance.</value>
        public static IDependencyInjectionFactory<T> Instance
        {
            get
            {
                if (null == factorySingleton)
                {
                    factorySingleton = (IDependencyInjectionFactory<T>)Activator.CreateInstance<TFactory>();
                }

                return factorySingleton;
            }
        }


        /// <summary>
        /// Gets the dependencies the factory uses to build.  ** THESE SHOULD NOT BE SET BY
        /// PRODUCTION CODE!!
        /// </summary>
        /// <value>The dependencies.</value>
        public static IDictionary<DependencyResolutionLocatorKey, object> Dependencies
        {
            get
            {
                return Instance.Dependencies;
            }
        }

        /// <summary>
        /// Used to clear the dependencies added by <see cref="AddDependency"/>.
        /// </summary>
        public static void ResetDependencies()
        {
            Instance.ResetDependencies();
        }

        /// <summary>
        /// Creates an instance of <typeparamref name="T"/>.
        /// </summary>
        /// <returns>Newly created <typeparamref name="T"/></returns>
        public static T Create()
        {
            return Instance.Create();
        }


        /// <summary>
        /// Creates an instance of <typeparamref name="T"/>.
        /// </summary>
        /// <param name="locatorKey">The key to use to lookup a registered object in the locator.</param>
        /// <returns>Newly created <typeparamref name="T"/></returns>
        public static T Create(string locatorKey)
        {
            return Instance.Create(locatorKey);
        }

        /// <summary>
        /// Adds the dependency which will be used in dependency injection.
        /// </summary>
        /// <param name="dependencyType">Type of the dependency.</param>
        /// <param name="id">The id.</param>
        /// <param name="value">The value.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")]
        public static void AddDependency(Type dependencyType, string id, object value)
        {
            Instance.AddDependency(dependencyType, id, value);
        }


        #endregion

        private readonly IUnityContainer container;

        private Dictionary<DependencyResolutionLocatorKey, object> dependencies;

        /// <summary>
        /// Initializes a new instance of the <see cref="DependencyInjectionFactory&lt;TFactory, T&gt;"/> class.
        /// </summary>
        /// <param name="defaultContainer">The default container.</param>
        protected DependencyInjectionFactory(IUnityContainer defaultContainer)
        {
            this.container = defaultContainer;
        }

		#region IDependencyInjectionFactory<T> Members



        void IDependencyInjectionFactory<T>.ResetDependencies()
        {
            TempLifetimeManager.DisposeAll();
            //this.container.Dispose();
        }

        T IDependencyInjectionFactory<T>.Create()
        {
            return this.Resolve(null);
        }

        T IDependencyInjectionFactory<T>.Create(string locatorKey)
        {
            return this.Resolve(locatorKey);
        }

        void IDependencyInjectionFactory<T>.AddDependency(Type dependencyType, string id, object value)
        {

            Dependencies.Add(new DependencyResolutionLocatorKey(dependencyType, id), value);

            container.RegisterInstance<T>(id, (T)value, new TempLifetimeManager());
        }

        IDictionary<DependencyResolutionLocatorKey, object> IDependencyInjectionFactory<T>.Dependencies
        {
            get
            {
                if (null == this.dependencies)
                {
                    this.dependencies = new Dictionary<DependencyResolutionLocatorKey, object>();
                }

                return this.dependencies;
            }
        }
        #endregion

        #region Protected Methods

        /// <summary>
        /// Configurations the override.
        /// </summary>
        /// <returns></returns>
        protected abstract IUnityContainerExtensionConfigurator ConfigurationOverride();

        /// <summary>
        /// Overridden by derived factories to provide the implementation of the factory.
        /// </summary>
        /// <returns></returns>
        protected virtual T Resolve(string idToBuild)
        {

            return container.Resolve<T>(idToBuild);
        }

        #endregion

        private class TempLifetimeManager : SelfControlledLifetimeManager
        {
            /// <summary>
            /// Disposes all.
            /// </summary>
            public static void DisposeAll()
            {
                DisposeAll<TempLifetimeManager>();
            }
        }

    }

    

}
